//go:build linux
// +build linux
package linux

import (
	"errors"
	"fmt"
	"fsnotify"
	"golang.org/x/sys/unix"
	"io"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"unsafe"
)

//Watcher watches a set of files, delvering events to a channel.
type Watcher struct {
	Events   chan fsnotify.Event
	Errors   chan error
	mu       sync.Mutex // Map access
	fd       int
	poller   *fdPoller
	watches  map[string]*watch // Map of inotify watches key:path
	paths    map[int]string    //map of watched paths key:watch descriptor
	done     chan struct{}     // channel for sending a quit message to the reader goroutine
	doneResp chan struct{}     // channel to respond to close
}

//NewWater estabish a new watcher with the underlying os and begins waiting for events.
func NewWatcher() (*Watcher, error) {
	//Create inotify fd
	fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC)
	if fd == -1 {
		return nil, errno
	}
	// create epoll
	poller, err := fsnotify.newFdPoller(fd)
	if err != nil {
		unix.Close(fd)
		return nil, err
	}
	w := &Watcher{
		fd:       fd,
		poller:   poller,
		watches:  make(map[string]*watch),
		paths:    make(map[int]string),
		Events:   make(chan fsnotify.Event),
		Errors:   make(chan error),
		done:     make(chan struct{}),
		doneResp: make(chan struct{}),
	}
	go w.readEvents()
	return w, nil
}
func (w *Watcher) isClosed() bool {
	select {
	case <-w.done:
		return true
	default:
		return false
	}
}

//Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
	if w.isClosed() {
		return nil
	}
	// Send 'close' signal to goroutine,and set the watcher to closed.
	// w.isclosed
	// sle
	// if
	// close
	// w.po.wake
	// <-w.done rep.
	close(w.done)
	// wake up goroutine
	w.poller.wake()
	//epoll 唤起 协程
	// wait for goroutine to close
	<-w.doneResp
	return nil
}

// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
	name = filepath.Clean(name)
	//文件filepath.Clean 清除文件linux winodws !phain9 windows linux unix sorais phaine mac os
	// cnetos untanbatuen readhat
	// unit32 1<< iota 2 4 8 16 32
	// op
	// strig
	// op&create = ceeate
	// var buf bytes.buffer
	//buf.writeong
	// c.writer.(http.Fluster).flush
	// c.write
	// flush
	// header tran-chunked
	// go - job-woker-disoy-iwre-adnnewdjr-adjob-dirnwrg=-gor0djhnwr-
	// for-
	// or
	// ore
	//o
	// 可以看出这样的方式是不是很舒服的方式
	// 进行方法内部都是首字符都是小驼峰的方式
	//class 大写都是可导出的方式 常量和变量和文件 压缩 管道 进程 线程 unix通信 编排 床 服务发现 服务熔断 计算
	//分布式 做一 右移动 方式
	// 游戏和什么方式 可以看出 windows linux cenian phain9 syuncin
	// c,copu\
	// 字符串切片使用 copy的方式 最好办退其他的指针地址的索引问题  maclloec
	// copy
	// s,len=nil
	// io.copy
	// strings.bulder
	// strings.joing
	// str,str,str +
	// bhtesbuter
	// witer
	// string
	// bytes,buffer
	// strings.nbuild
	// slice
	// 如果规定好的文件类型 系统类型 的常量就使用 toolib.createlib 即可
	// base kcgin map redis es rabiymq tasker conuser orm gorm dvorm
	// rencier
	// fd
	// chanle
	// mq
	// fhr
	//trafpo
	//grana
	// trafice
	// promuthus
	// api rpoc
	// api-rpc-map-mq
	// gho php xie
	//

	if w.isClosed() {
		return errors.New("innotify instance already closed")
	}
	const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
		unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
		unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
	var flags uint32 = agnosticEvents
	w.mu.Lock()
	defer w.mu.Unlock()
	watchEntry := w.watches[name]
	if watchEntry != nil {
		flags |= watchEntry.flags | unix.IN_MASK_ADD
	}
	wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
	if wd == -1 {
		return errno
	}
	if watchEntry == nil {
		w.watches[name] = &watch{wd: uint32(wd), flags: flags}
		w.paths[wd] = name
	} else {
		watchEntry.wd = uint32(wd)
		watchEntry.flags = flags
	}
	return nil
}

//Remove stops watching the named file or directory
func (w *Watcher) Remove(name string) error {
	name := filepath.Clean(name)
	// fetch the watch.
	w.mu.Lock()
	defer w.mu.Unlock()
	// Remove it from inotify.
	watch, ok := w.watches[name]
	if !ok {
		return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
	}
	// We successfully removed the watch if inotifyRewatch does't return an
	// error, we need to clean up our internal state to ensure it matches
	// inotify kernel state.
	delete(w.paths, int(watch.wd))
	delete(w.watches, name)

	//inotify_rm_watch will return EINVAL if the file has been deleted.
	// the notify will already have been removed.
	// watches and pathes are deleted in ignorelinux() implicitly and asyncronously
	//by calling inotify_rm_watch below e.g. readEvents() goroutine receives IN_IGNORE
	// so that EINVAL means that the wd is being rm_watch() ed or its file removed
	// by another thread and we have not received IN_IGNORE event.
	success, errno := unix.InotifyReWatch(w.fd, watch.wd)
	if success == -1 {
		//TODO: Perhaps it's not helpful to return an error here in every case.
		// the onlu two possible errrors are:
		// EBADF,which happens when w.fd is not a valid file descriptor of and kind.
		//EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor
		// watch descriptors are invalidated when they are removed explicitly or implicitly;
		// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted
		return errno
	}
	return nil
}

type watch struct {
	wd    uint32 // watch descriptors  as returned by the inoyofy_add_watch syscall
	flags uint32 // inotify flags of this watch see inotify7 for list of the valid flags.
}

//readEvents reads to the inotify file descriptor, converts the
// received events into event objects and sends them via the events channel
func (w *Watcher) readEvents() {
	var (
		buf   [unix.SizeofInotifyEvent * 4096]byte // buffer for a maximum of 4096 raw events
		n     int                                  // Number of bytes read with read()
		errno error                                // syscall errno
		ok    bool                                 // for poller.wait
	)
	defer close(w.doneResp)
	defer close(w.Errors)
	defer close(w.Events)
	defer unix.Close(w.fd)
	defer w.poller.close()

	for {
		// see if we have closed.
		//如果当前关闭直接推出
		if w.isClosed() {
			return
		}
		//epoll 等待信号
		// 如果有错误号 堵塞 如果w.errors可以err
		//如果这个时候被关闭了 则推出
		// 否则的话 continue
		// 如果 epoll没有等待 则继续西祠 循环
		// 从w.fd 读取 4095 自己内容
		// 如果eee则再次for 循环
		// 如果这个时候放在已经关闭
		//retrun
		// n 比较如果小于
		// n ==0
		// error =
		// n< 0
		// slse
		// selec
		// 堵塞
		// err
		// don
		// retun
		// contine
		ok, errno = w.poller.wait()
		if errno != nil {
			select {
			case w.Errors <- errno:
			case <-w.done:
				return

			}
			continue
		}
		if !ok {
			continue
		}
		n, errno = unix.Read(w.fd, buf[:])
		// if a signal interrupted execution, see if we've been asked to close, and try again.
		// signal.7.html :
		// before linux 3.8 reads from an inotoify7 file descriptor were not restatable
		if errno == unix.EINTR {
			continue
		}
		//unix.read migtht have veen woken up by close if so we're done.
		if w.isClosed() {
			return
		}
		if n < unix.SizeofInotifyEvent {
			var err error
			if n == 0 {
				// if eof is received. this should really never hanppen.
				err = io.EOF
			} else if n < 0 {
				// if an error occured while reading.
				err = errno
			} else {
				// read was too short.
				err = errors.New("notify: short read in readEvents")
			}
			select {
			case w.Errors <- err:
			case <-w.done:
				return

			}
			continue
		}
		var offset uint32 //偏移量
		// we don't konw how many events we just read into the buffer
		// while the offset points to at least one whole event...
		//使用unix.read 第一恶偏娱乐 buf[0] & 地址为 unix.innotiyEvbent
		//leix  w.iscode return ok,erorno := epoll.rewatiy
		// if er !=ni
		// slect {]
		// w.err<-errno

		// case w.done
		// return
		// contiue
		// !ok conytine
		// n,errno = uinix.readpunix]
		// err!=ni
		// contne
		// if isc.ose retyn
		// n
		// n<
		// n==0
		// var err error
		// errn<0
		// err =
		// select
		// w.error
		// w.done
		// rtunr
		// contne
		// offset<=uint(n-u)
		// raw:= safne.prony(&buf[offse[)(*ingror)
		// mask
		//
		for offset <= uint32(n-unix.SizeOfInotifyEvent) {
			// Point raw to the event in the buffer
			raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))

			mask := uint32(raw.Mask)
			nameLen := uint32(raw.len)

			if mask&unix.IN_Q_OVERFLOW != 0 {
				select {
				case w.Errors <- fsnotify.ErrEventOverflow:
				case <-w.done:
					return
				}
			}
			//if the event happed to the watched directory or the watched file,
			// the kernel does't append the filename to the event,but we would
			//like to always fill the name field with a valid filename
			// we retrieve the path of the watch from
			// the paths map
			w.mu.Lock()
			name, ok := w.paths[int(raw.Wd)]
			// IN_DELETE_SELF occurs when the file/directory being watched is removed
			//this is a sign to clean up the maps, otherwise we are no longer in sync
			// with the inotify
			if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
				delete(w.paths, int(raw.Wd))
				delete(w.watches, name)

			}
			w.mu.Unlock()

			if nameLen > 0 {
				//Point "bytes" at the first byte of the filename
				bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
				// The filename is padded with null bytes TrimRight() gets rid of those .
				name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
			}
			event := newEvent(name, mask)
			// send the evnts that are not ignored on the events channel
			if !event.ignoreLinux(mask) {
				select {
				case w.Events <- event:
				case <-w.done:
					return
				}
			}
			//move to the next event in the buffer
			offset += unix.SizeofInotifyEvent + nameLen
		}

		//

	}
}

// Certain types of events can be ignored and not sent over the events
// channel Such as events marked ignore by the kernerl, or modify events
// againest files that do not exists
func (e *fsnotify.Event) ignoreLinux(mask uint32) bool {
	//Ignore anything the notify API says to ignore
	if mask&unix.IN_IGNORED == unix.IN_IGNORED {
		return true
	}
	// if the close  close close lcoe for if return
	// ok,eoroo
	// eor 1=ni
	// slect
	// case return conie
	// /1ok conyi
	// n,err
	// ! conin
	//nm.ck
	// rtunr
	// n<
	// er
	//selec
	//fom
	// for fo<=(m-nur)
	// tasr = (*uni.pro)(unix.oifoior(bu&nu[off)
	// de
	// otherwise the event is ignored.
	// note this was input in place beause
	//it wa seen that a mogify
	// event was sent after the del;eye this ignores that modify
	// and argumes a delete will the come or has come if the file
	// doesn't exist
	// if
	if !(e.Op&fsnotify.Remove == fsnotify.Remove || e.Op&fsnotify.Rename == fsnotify.Rename) {
		_, statErr := os.Lstat(e.Name)
		return os.IsNotExist(statErr)
	}
	return false
}

//newEvent returns an platform-independent
// Event based on inotify
//
func newEvent(name string, mask uint32) fsnotify.Event {
	e := fsnotify.Event{Name: name}
	if mask&unix.IN_CREATE == unix.IN_CREATE {
		e.Op |= fsnotify.Create
	}
	if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
		e.Op |= fsnotify.Remove
	}
	if mask&unix.IN_MODIFY == unix.IN_MODIFY {
		e.Op |= fsnotify.Write
	}
	if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
		e.Op |= fsnotify.Rename
	}
	if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
		e.Op |= fsnotify.Chmod
	}
	return e
}
